﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Collections;

namespace SocketAsyncTest {

    public class ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync {

        const string PRINT_BIND9 = "00038580000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C01100020001000151800002C011C0110001000100015180000401020304";

        const string PRINT_DJBDNS = "00038500000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C011000200010003F4800002C011C0110001000100015180000401020304";
        const string PRINT_DJBDNS2 = "00030100000100000000000004686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C011000200010003F4800002C011C0110001000100015180000401020304";
        const string PRINT_PDNSD = "00038580000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C00C00020001000151800002C011C0110001000100015180000401020304";
        const string PRINT_POWERDNS = "00038500000100010000000004686F737406646F6D61696E03746C640000010001C00C0001000100000078000401020304";
        // BUG 20: Attributing sams buffer from pool to multiple Saea makes buffer look wrong.
        //00030100000100000000000004686F737406646F6D61696E03746C640000010001C00C0001000100000078000401020304
        const string expectedss = PRINT_BIND9;

        const string SERVER_BIND9 = "192.168.168.81";
        const string SERVER_DJBDNS = "192.168.168.82";
        const string SERVER_PDNSD = "192.168.168.83";
        const string SERVER_POWERDNS = "192.168.168.84";
        const string SERVER_PDNSD_MAC = "192.168.168.108";


        const string dnsserver = SERVER_BIND9;

        static int TotalSendTo = 0;
        static int TotalReceiveFrom = 0;
        static int TotalReceiveTimeout = 0;
        static int TotalSocketClosed = 0;
        static int ResponseSuccess = 0;
        static int AdressAlreadyInUse = 0;
        static int PortCollision = 0;

        static SocketAsyncEventArgsPool SaeaPool;
        static Semaphore SemaphoreConnections;
        static BufferManager BufferManager;
        static int BufferSize = 512;

        static Random RandomSrcPort;
        const int MinSrcPort = 1;
        const int MaxSrcPort = 49152;
        static byte[] PortInUse = new byte[65535];

        internal enum PortInUseEnum : byte {
            Free = 0,
            InUse = 1,
            Locked = 2
        }


        static EventHandler<SocketAsyncEventArgs> sendToCompleted = new EventHandler<SocketAsyncEventArgs>(saea_SendToCompleted);
        static EventHandler<SocketAsyncEventArgs> receiveFromCompleted = new EventHandler<SocketAsyncEventArgs>(saea_ReceiveFromCompleted);

        internal class MyUserToker {
            internal Socket Socket;
            internal bool SocketClosed;
            internal AutoResetEvent TimeoutEvent;
            internal EndPoint RemoteEndPoint;
        }

        public static void _Main(string[] argv) {

            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync bmtpsa = new ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync();
            for (int i = 156; i >= 0; i++) {
                bmtpsa.Test10000();
            }

        }

        public void Test10000() {

            // BUG 30 : 
            // int maxConnection = 10000; Everything is ok.
            //
            // int maxConnection = 30000;
            // SocketException: An invalid argument was supplied
            //  saea_SendToCompleted(Object sender, SocketAsyncEventArgs e): line 196
            //
            // netsh interface ipv4>show dynamicportrange udp
            // Protocol udp Dynamic Port Range
            // ---------------------------------
            // Start Port      : 49152
            // Number of Ports : 16384
            //
            // netsh interface ipv4>set dynamicportrange udp 1025 64510
            // Ok.
            // netsh interface ipv4>show dynamicportrange udp
            // Protocol udp Dynamic Port Range
            // ---------------------------------
            // Start Port      : 1025
            // Number of Ports : 64510

            DateTime start = DateTime.Now;
            Console.WriteLine("Started at: {0}", start);

            int maxConnection = 10000;

            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool = new SocketAsyncEventArgsPool(maxConnection);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SemaphoreConnections = new Semaphore(maxConnection, maxConnection);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager = new BufferManager(maxConnection * BufferSize, BufferSize);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.InitSaeaPool(maxConnection);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.RandomSrcPort = new Random();


            //int numSentTo = 1000000;
            int numSentTo = 1 * 1000 * 1000; // 
            for (int i = 0; i < numSentTo; i++) {

                ThreadPool.QueueUserWorkItem(new WaitCallback(TestSocketClient));
                //TestSocketClient(null);
            }

            Console.WriteLine("Pool Send Receive Success Timeout Closed AlreadyInUse Collision RandomAssignementFailed");
            do {

                Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7}",
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count,
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo,
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveFrom,
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.ResponseSuccess,
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveTimeout,
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed,
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.AdressAlreadyInUse,
                    ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.PortCollision
                    );

                Thread.Sleep(1000);
            } while (ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo != numSentTo);


            do {
                Thread.Sleep(1000);

                Console.WriteLine();
                Console.WriteLine("Saea in pool: {0}", ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count);
                Console.WriteLine("Missing saea: {0}", maxConnection - ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count);
                Console.WriteLine("Total sent: {0}", ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo);
                Console.WriteLine("Total received: {0}", ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveFrom);
                Console.WriteLine("Total timeout: {0}", ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveTimeout);
                Console.WriteLine("Total closed: {0}", ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed);
                Console.WriteLine("AdressAlreadyInUse: {0}", ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.AdressAlreadyInUse);
                Console.WriteLine("PortCollision: {0}", ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.PortCollision);

            } while (ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count != maxConnection);


            for (int i = 0; i < PortInUse.Length; i++) {
                if (PortInUse[i] == (byte)PortInUseEnum.Locked) {
                    Console.WriteLine("Port in use : {0}", i);
                }
            }
            // TODO: a background thread that poll Locked port 
            // and try to unlock them and avoid port consumption.
            // Also, you may prevent the dynamic port range from being blocked

            DateTime end = DateTime.Now;
            Console.WriteLine("Ended at: {0}", end);

            TimeSpan duration = end - start;
            Console.WriteLine("Duration: {0}h {1}m {2}s {3}ms",
                duration.Hours, duration.Minutes, duration.Seconds, duration.Milliseconds);


        }


        private static void InitSaeaPool(int maxConnection) {

            // Preallocate pool of SocketAsyncEventArgs objects.
            for (int i = 0; i < maxConnection; i++) {
                SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.SetBuffer(e);
                SaeaPool.Push(e);
            }

        }


        static string sendss = "00030100000100000000000004686F737406646F6D61696E03746C640000010001";
        static byte[] sends = StringToByteArray(sendss);

        public void TestSocketClient(object state) {


            EndPoint endpoint = new IPEndPoint(IPAddress.Parse(dnsserver), 53);
            MyUserToker userToken = new MyUserToker();
            userToken.RemoteEndPoint = endpoint;
            //userToken.Debug("TestSocketClient - new");

            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SemaphoreConnections.WaitOne();
            SocketAsyncEventArgs e = ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Pop();
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.ResetBuffer(e);
            //e.SetBuffer(buffer, 0, sends.Length);
            Array.Copy(sends, 0, e.Buffer, e.Offset, sends.Length);
            e.SetBuffer(e.Offset, sends.Length);

            e.RemoteEndPoint = endpoint;
            e.Completed += sendToCompleted;
            e.UserToken = userToken;

            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SetRandomSrcPort(socket);
            // BUG 81: I wanted to use ConnectAsync, but a bug prevent it from working:
            // http://social.msdn.microsoft.com/Forums/en/ncl/thread/e27dd1b7-44f8-49c7-bab3-56b72f0e4d2b
            //
            socket.Connect(endpoint);
            userToken.Socket = socket;

            try {
                //              userToken.Debug("TestSocketClient - SendToAsync");

                // BUG 80: receiving packet from remote host that do not match remoteendpoint !!!
                bool iopending = socket.SendToAsync(e);

                if (iopending == false) {
                    saea_SendToCompleted(this, e);
                }
            } catch (ObjectDisposedException ode) {

                //            userToken.Debug("TestSocketClient - ode");

                Interlocked.Increment(ref ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed);
                ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);
                return;
            }


        }

        private static void SetRandomSrcPort(Socket socket) {

            // BUG 60 : AddressAlreadyInUse ... when assigning random port
            ///socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);

            int port = 0;
            int collision = 0;
            int maxtry = 10;
            bool randomPortAssigned = false;

            while (!randomPortAssigned && maxtry >= 0) {
                maxtry--;

                lock (PortInUse) {

                    // BUG 40: Src port is incremental, as per attributed by kernel.
                    // add randomization
                    do {
                        port = RandomSrcPort.Next(MinSrcPort, MaxSrcPort);
                        collision++;
                    } while (PortInUse[port] != (byte)PortInUseEnum.Free);

                    if (collision > 1) {
                        Interlocked.Increment(ref PortCollision);
                    }
                    PortInUse[port] = (byte)PortInUseEnum.InUse;
                    EndPoint ep = new IPEndPoint(0, port);

                    try {

                        socket.Bind(ep);
                        randomPortAssigned = true;

                    } catch (SocketException se) {

                        if (se.SocketErrorCode != SocketError.AddressAlreadyInUse) {
                            throw;
                        } else {
                            Interlocked.Increment(ref AdressAlreadyInUse);
                            PortInUse[port] = (byte)PortInUseEnum.Locked;
                        }

                    }
                }
            }

            if (maxtry <= 0) {
                throw new InvalidOperationException("bind");
            }

        }

        static TimeSpan x3secs = new TimeSpan(0, 0, 0, 3);
        static TimeSpan x500milli = new TimeSpan(0, 0, 0, 0, 500);
        static TimeSpan x2_500sec = new TimeSpan(0, 0, 0, 2, 500);
        static TimeSpan Timeout = x3secs;
        const bool ExecuteOnlyOnce = true;
        const bool NonSignaled = false;

        static void saea_SendToCompleted(object sender, SocketAsyncEventArgs e) {

            if (e.LastOperation != SocketAsyncOperation.SendTo) {
                throw new InvalidOperationException(e.LastOperation.ToString());
            }
            if (e.SocketError != SocketError.Success) {
                throw new SocketException((int)e.SocketError);
            }
            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }


            MyUserToker userToken = e.UserToken as MyUserToker;
            //      userToken.Debug("saea_SendToCompleted");

            if (userToken.Socket == null) {

                //        userToken.PrintDebug();

                throw new ArgumentNullException("Socket");
            }
            Socket socket = userToken.Socket;
            // update counters, print stats
            Interlocked.Increment(ref ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo);

            // do the job
            // ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.FreeBuffer(e);
            //ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.SetBuffer(e);
            //e.SetBuffer(e.Offset, BufferSize);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.ResetBuffer(e);

            e.Completed -= sendToCompleted;
            e.Completed += receiveFromCompleted;

            // BUG 79: Random source port kernel bound
            //IPEndPoint endpoint = (IPEndPoint) socket.LocalEndPoint;
            //if (userToken.RequestedPort != endpoint.Port) {
            //    Console.WriteLine("Kernel bound Local end point: {0}", endpoint.Port);
            //}
            // LocalEndPoint is not set after SendTo

            try {
                //      userToken.Debug("saea_SendToCompleted - ReceiveFromAsync");
                bool iopending = socket.ReceiveFromAsync(e);
                if (iopending == false) {
                    saea_ReceiveFromCompleted(sender, e);
                }

            } catch (ObjectDisposedException) {

                //    userToken.Debug("saea_SendToCompleted - ode");

                Interlocked.Increment(ref ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed);
                ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);
                return;

            }

            AutoResetEvent timeoutEvent = new AutoResetEvent(NonSignaled);
            userToken.TimeoutEvent = timeoutEvent;
            //userToken.Debug("saea_SendToCompleted - timeoutevent");
            //userToken.Debug("saea_SendToCompleted - RegisterWaitForSingleObject");
            RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(
                timeoutEvent,
                new WaitOrTimerCallback(saea_ReceiveFromTimedOut),
                userToken,
                Timeout,
                ExecuteOnlyOnce
             );
            //userToken.Handle = handle;
            //userToken.Debug("saea_SendToCompleted - handle");

        }

        static void saea_ReceiveFromTimedOut(object state, bool timedOut) {

            // check da bitch
            if (timedOut == false) {
                return;
            }
            if (state == null) {
                throw new ArgumentNullException("state");
            }
            MyUserToker userToken = state as MyUserToker;
            //userToken.Debug("saea_ReceiveFromTimedOut");

            // synchronise
            lock (userToken) {

                if (userToken.SocketClosed) {
                    return;
                }
                if (userToken.Socket == null) {
                    return;
                    //throw new ArgumentNullException("Socket");
                }

                // update counters, print stats
                Interlocked.Increment(ref ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveTimeout);

                // do the job
                //  userToken.Debug("saea_ReceiveFromTimedOut - socket close");
                userToken.SocketClosed = true;
                CloseSocket(userToken.Socket);
                userToken.Socket = null;

            }


        }

        static void saea_ReceiveFromCompleted(object sender, SocketAsyncEventArgs e) {

            // check da bitch
            if (e.LastOperation != SocketAsyncOperation.ReceiveFrom) {
                throw new InvalidOperationException(e.LastOperation.ToString());
            }

            if (e.SocketError == SocketError.OperationAborted) {
                ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);
                return; // OperationAborted, get out of here !
            }

            if (e.SocketError != SocketError.Success) {
                ProcessError(e);
                return;
            }

            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }

            MyUserToker userToken = e.UserToken as MyUserToker;
            //userToken.Debug("saea_ReceiveFromCompleted");

            // synchronize
            lock (userToken) {

                if (!userToken.SocketClosed) {

                    if (userToken.Socket == null) {
                        throw new ArgumentNullException("Socket");
                    }

                    // BUG 80: We receive packets on binding endpoint from foreign
                    // computer (ie. different from remoteEndPoint)
                    // BECAUSE I FORGOT socket.connect...
                    if (e.RemoteEndPoint != userToken.RemoteEndPoint) {
                         throw new ArgumentOutOfRangeException("RemoteEndPoint");
                    }

                    // update counters, print stats
                    Interlocked.Increment(ref ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveFrom);

                    // do the job
                    //      userToken.Debug("saea_ReceiveFromCompleted - Unregister");
                    if (userToken.TimeoutEvent != null) { // immediate synchronous event do not need to timeout
                        //userToken.Handle.Unregister(null);
                        userToken.TimeoutEvent.Set();
                    }


                    string receivedss = ByteArrayToString(e.Buffer, e.Offset, e.BytesTransferred);
                    if (receivedss != ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.expectedss) {
                        Console.WriteLine();
                        Console.WriteLine(receivedss);
                    } else {
                        Interlocked.Increment(ref ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.ResponseSuccess);
                    }

                    //    userToken.Debug("saea_ReceiveFromCompleted - socket close");
                    userToken.SocketClosed = true;
                    CloseSocket(userToken.Socket);
                    userToken.Socket = null;


                }

            }


            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);

        }

        private static void ProcessError(SocketAsyncEventArgs e) {

            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }

            MyUserToker userToken = e.UserToken as MyUserToker;
            //userToken.Debug("saea_ReceiveFromCompleted");

            if (userToken.TimeoutEvent != null) { // immediate synchronous event do not need to timeout
                //userToken.Handle.Unregister(null);
                userToken.TimeoutEvent.Set();
            }

            ReleaseSaea(e);

        }
        private static void CloseSocket(Socket socket) {
            lock (PortInUse) {

                IPEndPoint ep = (IPEndPoint)socket.LocalEndPoint;
                socket.Shutdown(SocketShutdown.Both);
                socket.Close(0);
                if (ep != null) { // Socket bind failed, no localendpoint
                    int port = ep.Port;
                    PortInUse[port] = (byte)PortInUseEnum.Free;
                }
            }
        }

        private static void ReleaseSaea(SocketAsyncEventArgs e) {

            e.Completed -= receiveFromCompleted;
            e.Completed -= sendToCompleted;

            MyUserToker userToken = e.UserToken as MyUserToker;
            userToken.Socket = null;
            userToken.TimeoutEvent = null;
            e.UserToken = null;

            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.ResetBuffer(e);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Push(e);
            ConnectRandomSPortThreadedBufferManagerTimeoutPooledSocketAsync.SemaphoreConnections.Release();

        }


        public static byte[] StringToByteArray(string s) {

            string hex = s.Replace(" ", "");

            int NumberChars = hex.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            return bytes;

        }

        public static string ByteArrayToString(byte[] ba, int offset, int count) {
            string hex = BitConverter.ToString(ba, offset, count);
            return hex.Replace("-", "");
        }

    }
}
